/*
 * AbstractDialog
 * Version: 0.1
 * Date: 2009-03-01
 * Author: gp08-nhn
 */

package keyboardhero;

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;

abstract class AbstractDialog extends JDialog {
	
	//-------------------- VARIABLES --------------------
	
	// ××××× CORE VARIABLES ×××××
	
	private static final long serialVersionUID = -6487873323776346141L;
	private static final Dimension MINIMUM_SIZE = new Dimension(300, 150);
	protected String titleKey, configKey;
	protected boolean exitForEnterAndSpace = true;
	protected Runnable commandForEnter = null;
	protected boolean wasOpened = false;
	
	// ××××× ADAPTER VARIABLES ×××××
	
	/**
	 * This is the container adapter added to every container inside the dialog. This method will
	 * add the {@link #keyAdapter} recursively to every component inside the container. It is used
	 * to close the dialog at the press of an escape, enter or space key.
	 */
	private final ContainerAdapter containerAdapter = new ContainerAdapter() {
		public void componentAdded(ContainerEvent e) {
			addListeners(e.getChild());
		} // end method
		/* // unused
		public void componentRemoved(ContainerEvent e) {
		} // end method
		*/
	}; // end class
	
	/**
	 * This is the key adapter added to every component inside the dialog. It is used to close the
	 * dialog at the press of an escape, enter or space key.
	 * 
	 * @see #containerAdapter
	 */
	private final KeyAdapter keyAdapter = new KeyAdapter() {
		public void keyPressed(KeyEvent e) {
			switch(e.getKeyCode()) {
				case KeyEvent.VK_ESCAPE:
					close();
					break;
				case KeyEvent.VK_ENTER:
					if(commandForEnter != null) commandForEnter.run(); 
					else if(exitForEnterAndSpace) close();
					break;
				case KeyEvent.VK_SPACE:
					if(exitForEnterAndSpace) close();
					break;
			} // end switch
		} // end method
	}; // end class
	
	//-------------------- CORE FUNCTIONS --------------------
	
	public AbstractDialog(JFrame frame, String titleKey, String configKey) {
		this(frame, titleKey, configKey, false, true, null);
	}
	
	public AbstractDialog(JFrame frame, String titleKey, String configKey, boolean exitForEnter) {
		this(frame, titleKey, configKey, exitForEnter, true, null);
	}
	
	public AbstractDialog(JFrame frame, String titleKey, String configKey, boolean exitForEnter, boolean isResizeable) {
		this(frame, titleKey, configKey, exitForEnter, isResizeable, null);
	}
	
	public AbstractDialog(JFrame frame, String titleKey, String configKey, boolean exitForEnter, boolean isResizeable, Runnable commandForEnter) {
		// NOTE: Dialog.ModalityType requires Java 1.6 !
		super(frame, (titleKey == null ? null : Util.getMsg(titleKey)), Dialog.ModalityType.DOCUMENT_MODAL);
		
		this.titleKey = titleKey;
		this.configKey = configKey;
		this.exitForEnterAndSpace = exitForEnter;
		this.commandForEnter = commandForEnter;
		
		setVisible(false);
		setPosition();
		setResizable(isResizeable);
		
		addListeners(this);
		
		KeyboardHero.getDialogs().put(configKey, this);
	}
	
	/**
	 * This method adds actually the key adapter to the components. This method works recursively.
	 * 
	 * @param c
	 *            the component to which the key adapter should be applied.
	 * @see #keyAdapter
	 * @see #containerAdapter
	 */
	private void addListeners(Component c) {
		c.addKeyListener(keyAdapter);
		
		if(c instanceof Container) {
			Container container = (Container)c;
			container.addContainerListener(containerAdapter);
			for(Component child : container.getComponents()) {
				addListeners(child);
			} // end for
		} // end if
	} // end method
	
	//-------------------- FUNCTIONS TO OVERRIDE --------------------
	
	protected void setPosition() {
		setBounds(Util.getPropInt(configKey + "CooX"), Util.getPropInt(configKey + "CooY"), Util.getPropInt(configKey + "Width"), Util.getPropInt(configKey + "Height"));
		setMinimumSize(MINIMUM_SIZE);
	}
	
	protected abstract AbstractDialog getNewInstance(JFrame frame);
	
	//-------------------- EVENT FUNCTIONS --------------------
	
	protected void open() {
		wasOpened = true;
		try {
			setVisible(true);
		} catch(Throwable e) {
			dispose();
			getNewInstance((JFrame)this.getOwner()).open();
		}
	} // end method
	
	protected void updateTexts() {
		if(titleKey != null) setTitle(Util.getMsg(titleKey));
		Util.updateRecursiveText(this);
	}
	
	protected void updateUI() {
		Util.updateRecursiveUI(this);
	}
	
	/** Closes the dialog by making it invisible. */
	protected void close() {
		setVisible(false);
	} // end method
	
	public void setVisible(boolean b) {
		super.setVisible(b);
		if(!b) dispose();
	}
	
	/** Saves the position and size of the dialog. It is called when the application terminates. */
	protected void closure() {
		if(wasOpened) {
			Util.setProp(configKey + "CooX", getX());
			Util.setProp(configKey + "CooY", getY());
			Util.setProp(configKey + "Width", getWidth());
			Util.setProp(configKey + "Height", getHeight());
		}
	}
	
	//-------------------- DEBUGGING FUNCTIONS --------------------
	
	/**
	 * Creates a string containing the most important information about the game. This method is
	 * used only for debugging and testing purposes.
	 * 
	 * @return the created string.
	 */
	static String getString() { //$SUP-MCP$
		return "AbstractDialog()";
	} // end method
	
	/**
	 * This method serves security purposes. Provides an integrity string that will be checked by
	 * the {@link Connection#integrityCheck()} method; thus the application can only be altered if
	 * the source is known. Every class in the {@link keyboardhero} package has an integrity string.
	 * 
	 * @return the string of this class used for integrity checking.
	 */
	static String getIntegrityString() {
		return "ol+sj2SäłŁ*í~.1Asá-";
	} // end method
	
	/**
	 * The tester object of this class. It provides a debugging menu and unit tests for this class.
	 * Its only purpose is debugging or testing.
	 */
	final static Tester TESTER = new Tester("AbstractDialog", new String[] {"getString()"} /* end initialization */) { //$SUP-PPA$
		void menu(int choice) throws Exception {
			switch(choice) {
				case 5:
					System.out.println(getString());
					break;
				default:
					baseMenu(choice);
					break;
			} // end switch
		} // end method
		
		void runUnitTests() throws Exception {
			higherTestStart("AbstractDialog");
			
			testEq("getIntegrityString()", "ol+sj2SäłŁ*í~.1Asá-", AbstractDialog.getIntegrityString());
			
			higherTestEnd();
		} // end method
	}; // end class
	
	/**
	 * Starts the class's developing menu. If this build is a developer's one it starts the
	 * application in a normal way with the exception that it starts the debugging tool for this
	 * class as well; otherwise exits with an error message.
	 * 
	 * @param args
	 *            the arguments given to the program.
	 * @see KeyboardHero#startApp()
	 */
	public static void main(String[] args) {
		Tester.mainer(args, TESTER);
	} // end method
}
